/**
 * Drag.js 散装版
 * 
 * 这里记录写一个 drag 需要的基础函数
 * 
 * 已经有很多线程的 Drag 库了, 我们为啥还要做几个 dragjs 的基础库呢
 * 原因有 2
 * 
 *   * 作为一个基础知识点去学,去练,去提高.
 * 
 *   * 已有的 drag 库, 直接用还好, 但是我们有时候就有特殊的需求, 比如 吸附, 贴边,各种自定义操作, 等等一些特殊需求, 使用已有的 drag 库, 代码未必少, 未必更简洁. 维护起来未必更省力.
 *   
 */


/**
 *  Drag 预配置
 * 
 *  我们要进行一些设置, 防止出现意外情况
 */

// 拖拽的时候,进入边界会出现 scrollbar, 让人不爽, document.body 可以换成边界元素
document.body.style.overflow = 'hidden'

// 要拖拽的物体要设置 user-select:none; // 这样就不会出现拖不动,或者拖动放不开的 bug
element.style['user-select']='none'

/**
 * trans
 * 
 * 拖拽我们选择使用 transform 的方式, 而不是 left top 的方式
 * 是因为这样性能更好 ,效果更流畅
 * 
 * 要 drag 一个物体,首先要确定他的位置 transform
 * 
 * 获取 / 设置一个元素的 transform
 * 
 * transform get/set
 * 
 * 当仅输入元素的时候为获取, 
 * 当输入元素 + transObj 的时候为设置
 * 
 * transObj 对象写法
 * 
 * 这里涉及到一个 matrix 和transform 相互转换的问题
 * matrix 数学上表达 transform 位移形变等
 * transform 则是语言上表达,
 *      比如平移,旋转,缩放,斜切
 * {
          translate: [0, 0],
          rotate: 0,
          scale: [1, 1],
          skewX: 0,
    }
 */
  function trans(el, transObj) {
    if (!arguments.length) return
    if (el && !transObj) {
      let style = getComputedStyle(el)
      let t = style.transform
      if (t === 'none') {
        return {
          translate: [0, 0],
          rotate: 0,
          scale: [1, 1],
          skewX: 0,
        }
      } else {
        return matrix2transform(trans2matrix(t))
      }
    } else {
      if (transObj && typeof transObj === 'object') {
        el.style.transform = trans2string(transObj)
      }
    }

    function trans2matrix(tran) {
      let arr = tran.slice(7, -1).split(',')
      return {
        a: parseFloat(arr[0]),
        b: parseFloat(arr[1]),
        c: parseFloat(arr[2]),
        d: parseFloat(arr[3]),
        e: parseFloat(arr[4]),
        f: parseFloat(arr[5]),
      }
    }

    // 求点积
    function transDot(a, b) {
      return a[0] * b[0] + a[1] * b[1]
    }
    // normalize 化
    function transNormalize(a) {
      let k = Math.sqrt(transDot(a, a))
      a[0] /= k
      a[1] /= k
      return k
    }

    function transCombine(a, b, k) {
      a[0] += k * b[0]
      a[1] += k * b[1]
      return a
    }

    function matrix2transform(m) {
      const degrees = 180 / Math.PI
      let r0 = [m.a, m.b],
        r1 = [m.c, m.d],
        kx = transNormalize(r0)
      kz = transDot(r0, r1)
      ky = transNormalize(transCombine(r1, r0, -kz))
      return {
        translate: [m.e, m.f],
        rotate: Math.atan2(m.b, m.a) * degrees,
        scale: [kx, ky],
        skewX: (kz / ky) * degrees,
      }
    }

    function trans2string(transObj) {
      let transform = ''
      for (let k in transObj) {
        if (k === 'translate') {
          transform += `translate(${
            transObj[k][0] === 0 ? 0 : transObj[k][0] + 'px'
          },${transObj[k][1] === 0 ? 0 : transObj[k][1] + 'px'}) `
        } else if (k === 'rotate' || k === 'skewX') {
          transform += `${k}(${transObj[k]}deg) `
        } else if (k === 'scale') {
          transform += `${k}(${transObj[k]}) `
        }
      }
      return transform.trim()
    }
  }


/**
 *  虽然有html5 dragAPI , 但是我们更喜欢使用
 *  
 *  原生的 
 *   PC:mousedown mousemove mouseend
 *   Mobile: touchstart touchmove touchend
 * 
 *  为什么, 因为我们有着各种各样的需求, 为了这些需求的统一实现和控制
 *  我们选择最原始的, 而非封装好的 API ,是一个非常不错的选择 
 * 
 */

/**
 * 我们为我们要拖拽的元素添加一个标识符 drag/draggable/__drag 
 * 反正别和 其他的库,冲突就行
 * 
 * <div drag>拖拽对象</div>
 * 
 * 我们还可以进行一个更加常用的常用的操作,handler
 * <div drag="selector"></div>
 * 
 * 这时候, 目标 parents.matches(selctor) 的元素会触发 drag
 */
const DRAGFLAG = 'drag'

/**
 * 寻找正确的 target
 * 
 * 如上所述,我们触发的 target 的并不一样是要移动的 target
 * 所以我们要根据parents.matches(selctor) 找到真正的 target
 * 
 */

function findDragTarget(target){
    const dragflag = target.getAttribute(DRAGFLAG)
    if(dragflag === null){
        // 代表没有
        return false
    }else if(dragflag === ''){
        // 如果写了 DRAGFLAG,而没有值,则匹配的是 ''
        return target
    }else{
        // 那就是这里有 selector // 我们就要找 符合 parents 的 selector
        // 反正我们不会要 drag BODY 元素吧,不会吧
        while(target!== document.body){
            if(target.matches(dragflag)){
                return target
            }
            target = target.parentElement
        }

        if(target===document.body){
            return false
        }
    }
    
}

/**
 * 那我们就要开始布置事件了
 * 
 * mousedown/touchstart
 * 
 * 至于 触屏 鼠标双兼容
 * 我们采用 如果触发了 touchstart 那么就不会触发 mouse 事件,直到 touchend 发生
 * 
 * 使用 dragType 作为标记,默认为 mouse , 控制权在 touchstart touchend 手中
 */

let dragType = 'mouse'
let sX,sTX,sY,sTY,dX,dY,target

/**
 * touch 和 mouse 事件的不同之处
 * 
 * touch 多点触控,可以同时操作几个点
 * 我们要判断touch 的那个点在哪个元素上面
 * 
 * mouse 则是单点
 */




function getXY(e, target, type) {
    if (type==='touch') {
        let touches = e.touches
        let touchIndex = getTouchIndex(touches, target)
        if (touchIndex !== undefined) {
            return {
                x: e.touches[touchIndex].clientX,
                y: e.touches[touchIndex].clientY,
            }      
        
        }else{
            return null
        }
    } else {
        return {
            x: e.clientX,
            y: e.clientY,
        }
    }

    

    function getTouchIndex(touches, target) {
        let flag = false
        for (let i = 0, l = touches.length; i < l; i++) {
            let x = touches[i].clientX
            let y = touches[i].clientY
            if (pointInElement({ x, y }, target)) {
                return i
            }
        }
        return false
    }
    function pointInElement({ x, y }, el) {
        let rect = el.getBoundingClientRect()
        let xFlag = false,
            yFlag = false
        // 意思就是如果 x 不输入,那就默认为 true
        if (x !== 'undefined') {
            if (x >= rect.left && x <= rect.right) {
            xFlag = true
            }
        } else {
            xFlag = true
        }
        if (y !== 'undefined') {
            if (y >= rect.top && y <= rect.bottom) {
            yFlag = true
            }
        } else {
            yFlag = true
        }
        return xFlag && yFlag ? true : false
    }
}
document.addEventListener('touchstart',(e) =>{
    dragType = 'touch'
    e.preventDefault() // 阻止默认事件
    // init drag
    target = findDragTarget(e.target)
    if(!target) return
    let xy = getXY(e,target,'touch')
    if(!xy) return
    dragStart(target,xy)
})

document.addEventListener('touchend',(e) => {
    dragType = 'mouse'
    // reset drag
})
document.addEventListener('mousedown',(e) => {
    if(dragType === 'touch') return
    e.preventDefault() // 阻止默认事件
    // e.stopPropagation()

    target = findDragTarget(e.target)
    if(!target) return
    let xy = getXY(e,target,'mouse')
    if(!xy) return
    dragStart(target,xy)
},false)


function dragStart(target,xy){
    sX= xy.x
    sY= xy.y
    
    // 记录位置
    if(!target[DRAGFLAG]){
        target[DRAGFLAG] = trans(target)
    }

    sTX = target[DRAGFLAG].translate[0]
    sTY = target[DRAGFLAG].translate[1]

    document.addEventListener(dragType+'move',drag)
    document.addEventListener(dragType==='mouse'?'mouseup':'dragend',dragEnd)

}
function drag(e){
    let xy = getXY(e,target,dragType)
    if(!xy) return

    dX = xy.x-sX
    dY = xy.y-sY

    // 更新视图
    trans(target,{
        translate:[sTX+dX,sTY+dY]
    })
}
function dragEnd(){

    target[DRAGFLAG]={
        ...target[DRAGFLAG],
        translate:[sTX+dX,sTY+dY]
    }

    target=null
    document.removeEventListener(dragType+'move',drag)
    document.removeEventListener(dragType==='mouse'?'mouseup':'dragend',dragEnd)
}

// todo 多点触控事件的判定